package com.finalterm.gateway.filter;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * IP限流过滤器
 * 使用Sentinel热点参数功能实现IP级别的QPS限流
 *
 * 工作原理：
 * 1. 提取客户端真实IP
 * 2. 将IP作为参数传递给Sentinel
 * 3. Sentinel根据IP参数进行热点限流
 */
@Slf4j
@Component
public class IpExtractorFilter implements GlobalFilter, Ordered {

    public static final String CLIENT_IP_ATTR = "clientIp";

    // IP限流资源名称 - 这个名称会在Sentinel控制台中显示
    private static final String IP_RATE_LIMIT_RESOURCE = "gateway-ip-rate-limit";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 获取客户端真实IP
        String clientIp = getClientIp(request);

        // 将IP添加到exchange属性中，供后续使用
        exchange.getAttributes().put(CLIENT_IP_ATTR, clientIp);

        // 使用Sentinel进行IP级别的限流检查
        Entry entry = null;
        try {
            // 关键：使用热点参数限流，将IP作为参数传递给Sentinel
            // 使用EntryType.IN表示入口流量，clientIp作为热点参数
            entry = SphU.entry(IP_RATE_LIMIT_RESOURCE, EntryType.IN, 1, clientIp);

            log.debug("IP {} 通过限流检查，请求路径: {}", clientIp, request.getURI().getPath());
            return chain.filter(exchange);

        } catch (BlockException e) {
            // IP被限流，记录日志并继续（让后续的异常处理器处理）
            log.warn("IP {} 触发限流，请求路径: {}", clientIp, request.getURI().getPath());
            // 这里不直接返回错误，而是让Sentinel Gateway的异常处理器处理
            throw new RuntimeException("IP rate limit exceeded", e);

        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }

    /**
     * 获取客户端真实IP地址
     */
    private String getClientIp(ServerHttpRequest request) {
        // 1. 检查 X-Forwarded-For 头
        String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
        if (StringUtils.hasText(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) {
            String ip = xForwardedFor.split(",")[0].trim();
            return normalizeIp(ip);
        }

        // 2. 检查 X-Real-IP 头
        String xRealIp = request.getHeaders().getFirst("X-Real-IP");
        if (StringUtils.hasText(xRealIp) && !"unknown".equalsIgnoreCase(xRealIp)) {
            return normalizeIp(xRealIp);
        }

        // 3. 检查 Proxy-Client-IP 头
        String proxyClientIp = request.getHeaders().getFirst("Proxy-Client-IP");
        if (StringUtils.hasText(proxyClientIp) && !"unknown".equalsIgnoreCase(proxyClientIp)) {
            return normalizeIp(proxyClientIp);
        }

        // 4. 检查 WL-Proxy-Client-IP 头
        String wlProxyClientIp = request.getHeaders().getFirst("WL-Proxy-Client-IP");
        if (StringUtils.hasText(wlProxyClientIp) && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
            return normalizeIp(wlProxyClientIp);
        }

        // 5. 使用远程地址
        String remoteIp = request.getRemoteAddress() != null ?
            request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
        return normalizeIp(remoteIp);
    }

    /**
     * 标准化IP地址
     * 处理IPv6地址和本地地址的情况
     */
    private String normalizeIp(String ip) {
        if (ip == null || "unknown".equalsIgnoreCase(ip)) {
            return "unknown";
        }

        // 处理IPv6本地回环地址
        if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
            return "127.0.0.1";
        }

        // 处理IPv4映射的IPv6地址 (::ffff:192.168.1.1)
        if (ip.startsWith("::ffff:")) {
            return ip.substring(7);
        }

        // 处理其他IPv6地址，保持原样但记录日志
        if (ip.contains(":") && !ip.contains(".")) {
            log.debug("检测到IPv6地址: {}", ip);
            // 对于IPv6地址，可以选择保持原样或转换为简化形式
            return ip;
        }

        return ip;
    }

    @Override
    public int getOrder() {
        // 在Sentinel过滤器之前执行
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}
